(2024/04/06更新) 因應React在18後更新了許多不同的語法,更新後的教學之後將陸續放在 新的blog 中,歡迎讀者到該處閱讀,我依然會回覆這邊的提問
當我們得意地看著在前面的程式碼,卻發現一件不符合直覺的事情:
不管目前menu是開是關,外部按鍵永遠顯示「開啟選單」
要怎麼解決這個問題呢?我們現在想做的事情,其實可以用一句白話文來表達:
「我們想要一直看著isOpen這個變數,每當他被改變的那瞬間,就改變button的文字。」
新手在這個時候一般會想到無限迴圈,但這是一個很爛又很浪費效能的方法。
於是,過去的工程師就想出了「觀察者模式」。
「觀察者模式」的概念有點像是一個偵查兵,假設我們要一直看著的事叫做「開戰」好了,它做的事情就是:
對應在我們的程式碼就是
setIsOpen()
,並規定使用者只能用它來改變isOpen
。setIsOpen()
的定義內容中,在改變isOpen
後呼叫所有1中蒐集的函式,也就是:
let isOpenEffectListenerArr = [ funcA, funcB ];
function setIsOpen(){
isOpen = !isOpen;
//在這裡呼叫所有isOpenEffectListener
isOpenEffectListenerArr.forEach((func) => {
func();
});
}
這樣當isOpen被改變時,所有需要和isOpen有關的外部函式都能被觸發。
雖然Javascript與瀏覽器之間有客製Event的API,不過為了方便理解,我們這邊先簡單手刻一個這個過程。
首先,先定義用來蒐集外部Listener的函式
請注意listener可唯一可不唯一,取決於你想要的架構為何。在這裡我為了方便講解,
isOpenEffectListener
只會同時有一個listener。
this.setIsOpenEffectListener = function( listener ) {
this.isOpenEffectListener = listener;
}
接著在剛剛的this.setIsOpen
中,把isOpenEffectListener
放到設定完isOpen
的地方呼叫
const self = this;
this.setIsOpenEffectListener = function( listener ) {
this.isOpenEffectListener = listener;
}
this.setIsOpen = function() {
// 「!」會把true變false,false變true
isOpen = !isOpen;
/*----- 呼叫Listener -----*/
if(self.isOpenEffectListener) //避免isOpenEffectListener沒有被定義
self.isOpenEffectListener(isOpen);
if(isOpen){
menu.style.display = "block";
menuBtn.textContent="^";
}
else{
menu.style.display = "none";
menuBtn.textContent="V";
}
};
最後回到js/index.js,設定一個listener,用來改變button對應isOpen要顯式的文字
const controlBtn = document.createElement('button');
controlBtn.onclick = function(){
menuInstance.setIsOpen();
};
controlBtn.textContent = "開啟選單";
// 設定listener
menuInstance.setIsOpenEffectListener(function(isOpenNewValue){
if(isOpenNewValue)
controlBtn.textContent = "關閉選單";
else
controlBtn.textContent = "開啟選單";
})
還沒有耶,目前我們只是讓元件的功能更多樣化,但是使用上還能有更彈性的地方。下一篇我們要來討論切割架構